home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tricks of the Mac Game Programming Gurus
/
TricksOfTheMacGameProgrammingGurus.iso
/
More Source
/
Pascal
/
OffscreenToys
/
OffscreenToys 1.3
/
OffscreenToys.p
< prev
next >
Wrap
Text File
|
1995-01-11
|
24KB
|
752 lines
{--------- OFFSCREEN TOYS 1.3 ---------}
{by Ingemar Ragnemalm 1994}
{An attempt to make a simple, small, stand-alone, compatible offscreen animation demo.}
{I made this since many people want to learn the internals of animation, which packages}
{like SAT (Sprite Animation Toolkit) and aren't good for, even if I had included source.}
{Source code examples should be small!}
{Some good points with Offscreen Toys:}
{- It's free, with full source code.}
{- It's complete; no other libraries needed.}
{- It's small, both the source and the compiled binary.}
{- It is a real Mac application with decent event processing.}
{- It works both with and without Color QuickDraw!}
{- Demonstrates the use of fixed-point numbers for speed and positions.}
{Some bad points:}
{- It's not designed for re-using for complicated animation projects (which SAT is). The list}
{of sprites is an array with no possibility to handle different kinds of sprites. (That's up to}
{you to add if you want to build upon this.)}
{- It doesn't give the maximum speed possible. You can make QuickDraw draw faster (see}
{the "boosted" version), and you can draw directly to screen.}
{- It is neither MultiFinder-aware nor AppleEvent-aware.}
{- The sprite movement isn't as trivial as it could have been, especially the collision handling}
{but also the fixed-point positions, but hey, I need some fun too! Do we want a version that is}
{strictly integer-based?}
{- A few things that should be const'ed are hard-coded, i.e. the size of the icon (32x32) and}
{the number of fixed-point "binary decimals" (4).}
{What can you use it for?}
{- Learn the basics of sprite animation.}
{- Use the glue code for making your program work on non-color Macs. Don't you believe in}
{those old Macs any more? If you make a networked game, you should.}
{- Use as skeleton program.}
{Offscreen Toys 1.1 adds better collision handling, making the marbles bounce in a more}
{realistic way.}
{Offscreen Toys 1.2 fixes (?) the bug that sometimes caused a marble to disappear for a}
{few seconds, has better error reporting and works with CodeWarrior. (Original version}
{was Think Pascal only)}
{The "boosted" version uses CopyBits from an offscreen to draw the sprites rather than}
{PlotCIcon. This should speed things up quite a bit.}
{Version 1.3 takes out all reuseable code to a separate file. I hope that will make each part}
{easier to understand and makes the reuseable code easily used from other programs.}
program OffscreenToysPlus;
uses
{$IFC UNDEFINED THINK_PASCAL}
Types, QuickDraw, Events, Windows, Dialogs, Fonts, DiskInit, TextEdit, Traps, Desk, Memory, SegLoad, Scrap, ToolUtils, OSEvents, OSUtils, Menus, Resources, Packages, {}
{$ENDC}
QDOffscreen, Sound, OffscreenToysUtils;
{ --- PART 1: Variables and constants: -----------------------------------------}
const
kAppleID = 128;
kFileID = 129;
kMBarHeight = 20; { We assume 20 pixels menu bar for window sizing and dragging.}
kWindID = 128; { Window resource ID }
kAboutAlertID = 128; { Alert resource ID }
kSpriteNumber = 5; { Number of moving objects }
var
gWhoa: Boolean; {True when we want to quit}
gCollisionFlag: Boolean; {Collisions or not?}
{Menu handles}
appleMenu, fileMenu: MenuHandle;
{The window we'll be using}
gWind: WindowPtr;
{Our two offscreens:}
offScreen, backScreen: GrafPtr;
{A cicn handle}
gCicn: CIconHandle;
{Sprite information. In real games, I prefer making a linked list of records, like I do in}
{SAT, and a lot more information for each, but here we want it *simple*.}
{- position: The positions in local coordinates for the window}
{- fixedPos: 16 times position, which gives us fixed-point numbers}
{- speed: Speed vectors that is added to fixedPos for every frame}
{- r: Rectangles used in drawing, for remembering what part of the screen to update}
position, fixedPos: array[1..kSpriteNumber] of Point;
speed: array[1..kSpriteNumber] of Point;
r: array[1..kSpriteNumber] of Rect;
{A longint that shows how big the "bowl" is (assumes circular, not oval, bowl!):}
gBowlSize: Longint;
{ --- PART 2: Various general, reuseable routines, mostly glue: ---------------------}
{All except Rand moved to OffscreenToysUtils}
{Rand: simply make a random number between 0 and range-1.}
function Rand (range: integer): integer;
{var ulrik: integer;}
begin
{This was the only place where I got bitten by CodeWarrior bugs:}
{The following works with MWP:}
{ulrik := Random;}
{ulrik := ulrik mod range;}
{ulrik := abs(ulrik);}
{Rand := ulrik;}
{This too:}
{ulrik := Random;}
{Rand := abs(ulrik mod range);}
{And, THIS works: !!!}
Rand := abs(Integer(Random) mod range);
{This doesn't:}
{Rand := abs(Random mod range)}
{This neither:}
{Rand := BitAnd(Random mod range, $7fff);}
end;
{ --- PART 3: Application specific routines: ---------------------------------}
{mouse clicks, keydowns, background tasks and update events: This is where all}
{the action is. :-) I include some empty procedures for you to fill in if you want to}
{use this demo as application shell.}
{Mouse click in window content}
procedure DoMouse (where: Point; modifiers: Longint);
begin
end;
{Keydown.}
procedure DoKey (theKey: Char; modifiers: Longint);
begin
end;
{DoBackground: repeating tasks - this is called repeatedly, after every event we get.}
{Note: If you are making a really Mac-friendly program, this is where you should drive}
{the animation. However, it is hard to get high framerate then, since other programs}
{(the Finder included) will process events which will make it less smooth.}
procedure DoBackground;
const
kWallBounce = 7; {1/10-ths of speed kept after wallbounce}
kBallDiameterSquared = 32 * 32; {Diameter 32, squared}
var
tmpRect: Rect;
i, j: integer;
vector: Point;
saveGD: GDHandle;
savePort: GrafPtr;
tmpSpeed: Point;
squaredLength: Longint;
p1, p2, n1, n2: Point;
{Split a vector (v1) into one component parallell to another vector (direction) and one}
{orthogonal to it.}
procedure SplitVector (v1, direction: Point; var parallell, normal: Point);
var
l2, v1pr: Longint;
begin
{parallell := direction * (v1 DOT direction) /|direction|**2}
{normal := v1 - parallell}
l2 := direction.h * direction.h + direction.v * direction.v; {Squared length of "direction"}
v1pr := v1.h * direction.h + v1.v * direction.v; {Scalar product}
parallell.h := direction.h * v1pr div l2;
parallell.v := direction.v * v1pr div l2;
normal.h := v1.h - parallell.h;
normal.v := v1.v - parallell.v;
end;
{A rather boring subroutine that moves the sprites i and j away from each other.}
{I almost didn't want to put this in, making the demo unnecessarily big, but I wanted}
{decent collisions. If anyone can suggest a better (simpler) collision handling for circular}
{objects, I'd be happy to put it in.}
{I use a line drawing algorithm for it. I'm sure there are better ways. This is of questionable}
{value as reuseable code - depends on your application.}
procedure Separate (i, j: integer);
var
initVector, nowVector: Point;
absH, absV: integer;
moveH, moveV: integer;
frac: integer;
{Normal signum function (which I don't think is in the libs)}
function Sgn (x: integer): integer;
begin
if x > 0 then
Sgn := 1
else if x < 0 then
Sgn := -1
else
Sgn := 0;
end;
begin {Separate}
frac := 0;
initVector.h := position[i].h - position[j].h;
initVector.v := position[i].v - position[j].v;
absH := abs(initVector.h);
absV := abs(initVector.v);
moveH := Sgn(initVector.h);
moveV := Sgn(initVector.v);
if moveH = 0 then
if moveV = 0 then
moveV := 1;
repeat
if absH > absV then
begin
position[i].h := position[i].h + moveH;
position[j].h := position[j].h - moveH;
frac := frac + absV;
if frac > absH then
begin
position[i].v := position[i].v + moveV;
position[j].v := position[j].v - moveV;
frac := frac - absH;
end
end
else
begin
position[i].v := position[i].v + moveV;
position[j].v := position[j].v - moveV;
frac := frac + absH;
if frac > absV then
begin
position[i].h := position[i].h + moveH;
position[j].h := position[j].h - moveH;
frac := frac - absV;
end
end;
nowVector.h := position[i].h - position[j].h;
nowVector.v := position[i].v - position[j].v;
until longint(nowVector.h) * nowVector.h + longint(nowVector.v) * nowVector.v > kBallDiameterSquared;
fixedPos[i].h := BSL(position[i].h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
fixedPos[i].v := BSL(position[i].v, 4);
fixedPos[j].h := BSL(position[j].h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
fixedPos[j].v := BSL(position[j].v, 4);
end;
begin {DoBackground}
OTGetGWorld(savePort, saveGD);
{1: Erase all sprites from offScreen}
{Note: We keep the rectangles r[i] for erasing on the screen, later.}
OTSetGWorld(offScreen, nil);
for i := 1 to kSpriteNumber do
begin
r[i] := gCicn^^.iconBMap.bounds;
OffsetRect(r[i], position[i].h, position[i].v);
CopyBits(backScreen^.portBits, offScreen^.portBits, r[i], r[i], srcCopy, nil);
end;
{2: Change the position and speed}
for i := 1 to kSpriteNumber do
begin
{Modify fixed-point position by speed}
fixedPos[i].h := fixedPos[i].h + speed[i].h;
fixedPos[i].v := fixedPos[i].v + speed[i].v;
{Make position by shifting away the 4 binary "decimals"}
if fixedPos[i].h >= 0 then
position[i].h := BSR(fixedPos[i].h, 4)
else
position[i].h := BitOr(BSR(fixedPos[i].h, 4), $f000);
if fixedPos[i].v >= 0 then
position[i].v := BSR(fixedPos[i].v, 4)
else
position[i].v := BitOr(BSR(fixedPos[i].v, 4), $f000);
{Keep inside the window}
if fixedPos[i].h + speed[i].h < 0 then
speed[i].h := abs(speed[i].h) * kWallBounce div 10 + 1;
if fixedPos[i].v + speed[i].v < 0 then
speed[i].v := abs(speed[i].v) * kWallBounce div 10 + 1;
if position[i].h + gCicn^^.iconBMap.bounds.right > offScreen^.portRect.right then
speed[i].h := -abs(speed[i].h) * kWallBounce div 10 - 1;
if position[i].v + gCicn^^.iconBMap.bounds.bottom > offScreen^.portRect.bottom then
speed[i].v := -abs(speed[i].v) * kWallBounce div 10 - 1;
{Are we in the bowl? If we are, accelerate towards the center.}
vector.h := position[i].h + 16 - BSR(offScreen^.portRect.right, 1);
vector.v := position[i].v + 16 - BSR(offScreen^.portRect.bottom, 1);
if (vector.h * vector.h + vector.v * vector.v) < gBowlSize then
begin
speed[i].h := speed[i].h - vector.h div 2;
speed[i].v := speed[i].v - vector.v div 2;
end;
end; {position/speed loop}
{Check for collisions}
if gCollisionFlag then
for i := 1 to kSpriteNumber - 1 do {For all objects except the last}
for j := i + 1 to kSpriteNumber do {compare its position to all following objects}
begin
{Find the vector between them}
vector.h := position[i].h - position[j].h;
vector.v := position[i].v - position[j].v;
squaredLength := longint(vector.h) * vector.h + longint(vector.v) * vector.v;
{If it is shorter than the diameter of a ball…}
if squaredLength >= 0 then {WHY do I get negative values here? Or is that a CW bug that has been fixed?}
if squaredLength < kBallDiameterSquared then
begin
{Move them away from each other}
Separate(i, j);
{Swap the speed components that are parallell to "vector" (this allows for "touches", very}
{nice and realistic bounces)}
SplitVector(speed[i], vector, p1, n1);
SplitVector(speed[j], vector, p2, n2);
speed[j].h := p1.h + n2.h;
speed[j].v := p1.v + n2.v;
speed[i].h := p2.h + n1.h;
speed[i].v := p2.v + n1.v;
{Old Offscren Toys just swapped the speed, as commented out below. This is not as realistic.}
{tmpSpeed := speed[i];}
{speed[i] := speed[j];}
{speed[j] := tmpSpeed;}
{Play a sound. REMOVED since this isn't a sound demo.}
{if gSoundFlag then}
{if SndPlay(nil, GetNamedResource('snd ', 'Kgck'), false) <> noErr then [Ignore error}
end;
end; {collision loop}
{3: Draw sprites in offScreen}
{Note: PlotCIcon (OTPlotCicn) is not very fast! We can speed it up my pre-drawing them in some}
{offscreen, and CopyBits them from there.}
for i := 1 to kSpriteNumber do
begin
tmpRect := gCicn^^.iconBMap.bounds;
OffsetRect(tmpRect, position[i].h, position[i].v);
OTPlotCicn(gCicn, offScreen, tmpRect);
end;
{4: Copy sprites to the screen (gWind) - both old and new position!}
{Note: Depending on what limitations we have on movement, we may be able to avoid the multiple}
{CopyBitsing here. E.g. if sprites always move a maximum of 2 pixels, we can copy a 2 pixels}
{larger area, etc.}
OTSetGWorld(gWind, saveGD); {What GD is most appropriate here?}
for i := 1 to kSpriteNumber do
begin
CopyBits(offScreen^.portBits, gWind^.portBits, r[i], r[i], srcCopy, nil);
r[i] := gCicn^^.iconBMap.bounds;
OffsetRect(r[i], position[i].h, position[i].v);
CopyBits(offScreen^.portBits, gWind^.portBits, r[i], r[i], srcCopy, nil);
end;
OTSetGWorld(savePort, saveGD);
end; {DoBackground}
{DoUpdate: handle update events, in this case by copying offScreen to the screen (gWind).}
{Note to beginners: A program without update events processing is not a real Mac program!}
{All drawing you do must reach the update event handler in some way, or it might be lost,}
{or worse, partially erased, which is really ugly.}
procedure DoUpdate;
var
saveGD: GDHandle;
savePort: GrafPtr;
begin
OTGetGWorld(savePort, saveGD);
SetPort(gWind); {or OTSetGWorld(gWind, GetMainDevice), in case I forget to et back the device?}
BeginUpdate(gWind);
{Do drawing here - in this case a CopyBits}
CopyBits(offScreen^.portBits, gwind^.portBits, gwind^.portRect, gwind^.portRect, srcCopy, nil);
EndUpdate(gWind);
OTSetGWorld(savePort, saveGD);
end;
{DoAppleMenu and DoFileMenu: handle menu selections}
procedure DoAppleMenu (item: integer);
var
str: Str255;
h: Handle;
saveGD: GDHandle;
savePort: GrafPtr;
ignore: integer;
begin
if item = 1 then
begin
if Alert(kAboutAlertID, nil) = 1 then
; {Ignore result}
end
else
{Apple menu other than "About": Code from TransSkel}
begin
OTGetGWorld(savePort, saveGD); {I guess GetPort would be ok}
GetItem(appleMenu, item, str);
SetResLoad(false);
h := GetNamedResource('DRVR', str);
SetResLoad(true);
if h <> nil then
begin
ResrvMem(SizeResource(h) + $1000);
ignore := OpenDeskAcc(str);
end;
OTSetGWorld(savePort, saveGD);
end;
end; {DoAppleMenu}
procedure DoFileMenu (item: integer);
var
start, finish, frames: Longint;
fpsStr: Str255;
begin
case item of
1:
{Run animation without event processing until the user clicks the mouse}
{Note: This runs the animation at maximum speed. In real programs, we}
{must limit the speed with the system clock, e.g. inspect TickCount.}
begin
start := TickCount;
frames := 0;
while not Button do
begin
DoBackground;
frames := frames + 1;
end;
finish := TickCount;
NumToString(frames * 60 div (finish - start), fpsStr);
ParamText(fpsStr, ' frames/second', '', '');
if Alert(129, nil) = 1 then
;
end;
2:
begin
gCollisionFlag := not gCollisionFlag;
CheckItem(fileMenu, 2, gCollisionFlag);
end;
{Set the flag that tells the program to quit.}
4:
gWhoa := true;
end; {case}
end; {DoFileMenu}
{ --- PART 4: Event processing: -----------------------------------------}
{MenuSelection: Menu selection by mouse or command-key:}
procedure MenuSelection (whatSelection: longInt);
begin
case HiWord(whatSelection) of
kAppleID:
DoAppleMenu(LoWord(whatSelection));
kFileID:
DoFileMenu(LoWord(whatSelection));
end; {case}
HiLiteMenu(0);
end;
{MainLoop: get and process events. This is the boring standard part of all programs. I prefer}
{using TransSkel to get rid of it. I don't here since I want this code to be stand-alone.}
procedure MainLoop;
const
kSleep = 5; {Real programs may modify the sleep time depending on whether or not they are in the front}
var
hasEvent: Boolean;
theEvent: EventRecord;
theKey: Char;
whatSelection: Longint;
whichPart: integer;
whichWindow: WindowPtr;
r: rect;
begin
{Get the next event. Use WaitNextEvent if possible.}
if gHasWNE then
hasEvent := WaitNextEvent(everyEvent, theEvent, kSleep, nil)
else
begin
SystemTask;
hasEvent := GetNextEvent(everyEvent, theEvent);
end;
{OK, so what happened then?}
if hasEvent then
case theEvent.what of
mouseDown:
begin
whichPart := FindWindow(theEvent.where, whichWindow);
case whichPart of
inMenuBar:
begin
whatSelection := MenuSelect(theEvent.where);
MenuSelection(whatSelection);
end;
inSysWindow:
SystemClick(theEvent, whichWindow);
inGoAway:
if (TrackGoAway(whichWindow, theEvent.where)) then
gWhoa := true;
inDrag:
begin
if (whichWindow <> FrontWindow) and (BitAnd(theEvent.modifiers, cmdKey) = 0) then
SelectWindow(whichWindow);
{$IFC UNDEFINED THINK_PASCAL}
r := qd.screenBits.bounds; {How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
{$ELSEC}
r := screenBits.bounds; {How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
{$ENDC}
r.top := r.top + kMBarHeight; { Skip down past menu bar }
InsetRect(r, 4, 4);
DragWindow(whichWindow, theEvent.where, r);
end;
inGrow:
; {Ignored - we don't resize}
inContent:
if (whichWindow <> FrontWindow) then
SelectWindow(whichWindow)
else
DoMouse(theEvent.where, theEvent.modifiers); {Go to application-specific mouse down handling}
end; {case whichPart}
end; {mouseDown}
keyDown, autoKey:
begin
theKey := char(BitAnd(theEvent.message, charCodeMask));
if (BitAnd(theEvent.modifiers, cmdKey) <> 0) then
MenuSelection(MenuKey(theKey))
else
DoKey(theKey, theEvent.modifiers);
end;
updateEvt:
{There's only one window to bother with here, but let's make sure that's the one the Mac wants to update.}
if WindowPtr(theEvent.message) = gWind then
DoUpdate;
{Handle disk inserts like TransSkel.}
diskEvt:
if (HiWord(theEvent.message) <> noErr) then
begin
DILoad;
if DIBadMount(Point($00400040), theEvent.message) = 0 then
;
DIUnload;
end; {diskEvt}
otherwise {Other events are ignored}
end; {case}
DoBackground;
end;
{ --- PART 5: Initializations: -----------------------------------------}
{OTInit: Initialize global flags, menus and window}
procedure OTInit;
const
{Trap numbers}
_WaitNextEvent = $A860;
_GetCIcon = $AA1E; {E.g. any Color QuickDraw routine}
k32bQD = $AB1D;
_SndPlay = $A805;
var
appleStr: Str255;
begin
{In case this isn't Think Pascal we have to make the standard inits ourselves.}
{$IFC UNDEFINED THINK_PASCAL}
InitGraf(@qd.thePort);
InitFonts;
InitWindows;
InitMenus;
TEInit;
InitDialogs(nil);
{InitCursor;}
MaxApplZone;
{$ENDC}
{Over to more interesting stuff}
OTInitGlobals; {Init OffscreenToysUtils}
gWhoa := false;
gCollisionFlag := false;
{We could check with Gestalt instead, but that isn't necessary here since we aren't using any}
{optional services (like QuickTime).}
{$IFC UNDEFINED THINK_PASCAL}
qd.randSeed := TickCount; {Seed the random number generator - TickCount is good enough.}
{$ELSEC}
randSeed := TickCount; {Seed the random number generator - TickCount is good enough.}
{$ENDC}
{Get the window, a color window if we are going to use color.}
if gColorQDFlag then
gWind := GetNewCWindow(kWindId, nil, WindowPtr(-1))
else
gWind := GetNewWindow(kWindId, nil, WindowPtr(-1));
{Some menus. We could read these from resources.}
appleMenu := NewMenu(kAppleID, stringof(char($14)));
AppendMenu(appleMenu, 'About OffscreenToys…;(-');
AddResMenu(appleMenu, 'DRVR');
InsertMenu(appleMenu, 0); { put apple menu at end of menu bar }
fileMenu := NewMenu(kFileID, 'File');
AppendMenu(fileMenu, 'Try max speed;Collisions;(-;Quit/Q');
InsertMenu(fileMenu, 0); { put file menu at end of menu bar }
DrawMenuBar;
end;
{OTOffscreensInit: Initialize offscreen grafports (worlds) and draw in them.}
procedure OTOffscreensInit;
var
saveGD: GDHandle;
savePort: GrafPtr;
thePat: PixPatHandle;
r: Rect;
i: integer;
colorFlag: Boolean;
const
patID = 128;
{A little routine for setting the forecolor with a single line.}
procedure OTForeColor (red, green, blue: integer);
var
theColor: RGBColor;
begin
theColor.red := red;
theColor.green := green;
theColor.blue := blue;
RGBForeColor(theColor);
end;
begin {OTOffscreensInit}
OTGetGWorld(savePort, saveGD);
OTNewGWorld(offScreen, gWind^.portRect);
OTNewGWorld(backScreen, gWind^.portRect);
OTSetGWorld(backScreen, nil);
{Do some drawing in backScreen. First, we paint a pattern (using a 'ppat' resource):}
{For drawing the background, let's make a local flag that tells us if we shold draw}
{b/w patterns or color ones.}
if gColorQDFlag then
colorFlag := (CGrafPtr(backScreen)^.portPixMap^^.pixelSize > 1)
else
colorFlag := false;
if colorFlag then
begin
thePat := GetPixPat(patID);
PenPixPat(thePat)
end
else
begin
thePat := PixPatHandle(GetResource('ppat', patID));
PenPat(thePat^^.pat1Data);
end;
PaintRect(backScreen^.portRect);
PenNormal;
{Then we draw some circles.}
r := backScreen^.portRect;
InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
gBowlSize := longint(r.right - r.left) * (r.right - r.left) div 4; {Tells how big the "bowl" is!}
if colorFlag then
begin
OTForeColor(-10000, -10000, -10000);
PaintOval(r);
end
else
{$IFC UNDEFINED THINK_PASCAL}
FillOval(r, qd.ltGray);
{$ELSEC}
FillOval(r, ltGray);
{$ENDC}
InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
if colorFlag then
begin
OTForeColor(-25000, -25000, -25000);
PaintOval(r);
end
else
{$IFC UNDEFINED THINK_PASCAL}
FillOval(r, qd.gray);
{$ELSEC}
FillOval(r, gray);
{$ENDC}
InsetRect(r, (r.right - r.left) div 6, (r.bottom - r.top) div 6);
if colorFlag then
begin
OTForeColor(20000, 20000, 20000);
PaintOval(r);
end
else
{$IFC UNDEFINED THINK_PASCAL}
FillOval(r, qd.dkGray);
{$ELSEC}
FillOval(r, dkGray);
{$ENDC}
InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);
if colorFlag then
OTForeColor(0, 0, 0);
PaintOval(r);
{Done drawing!}
{For your own hacks, consider using a PICT resource and use GetPicture and DrawPicture to draw the}
{background. Note that you'll need both a color and a b/w picture if you want it to look good in b/w.}
OTSetGWorld(offScreen, nil);
CopyBits(backScreen^.portBits, offScreen^.portBits, backScreen^.portRect, backScreen^.portRect, srcCopy, nil);
OTSetGWorld(savePort, saveGD);
{Get the cicn resource}
{Note: You can, of course, use several cicns and switch between.}
gCicn := OTGetCicn(128);
{Initialize the sprite information arrays:}
for i := 1 to kSpriteNumber do
begin
position[i].h := Rand(offScreen^.portRect.right - 32);
position[i].v := (i - 1) * (offScreen^.portRect.bottom - 32) div 5 + Rand((offScreen^.portRect.bottom - 32) div 5);
fixedPos[i].h := BSL(position[i].h, 4);
fixedPos[i].v := BSL(position[i].v, 4);
speed[i].h := Random mod 32;
speed[i].v := Random mod 32;
end;
end;
{ --- MAIN PROGRAM BODY: -----------------------------------------}
begin
OTInit; {General initializations}
OTOffscreensInit; {Set up the offscreen grafports}
InitCursor; {Set the cursor to arrow in case it isn't.}
{Run until quit or click in the close box.}
repeat
MainLoop;
until gWhoa;
{No cleanup is necessary here.}
{We could DisposeGWorld, but that isn't necessary when we are quitting.}
end.